package cn.remex.web.aspect;

import cn.remex.RemexConstants;
import cn.remex.core.RemexContext;
import cn.remex.core.exception.ServiceCode;
import cn.remex.core.util.Assert;
import cn.remex.core.util.Param;
import cn.remex.core.util.ThreadHelper;
import cn.remex.db.Database;
import cn.remex.db.model.SysStatus;
import cn.remex.db.sql.Sort;
import cn.remex.web.service.BusinessService;
import cn.remex.web.service.appbeans.AsyncCvo;
import cn.remex.web.service.appbeans.AsyncRvo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import static cn.remex.db.rsql.RsqlConstants.SysStatusEnum.*;
import static cn.remex.db.sql.WhereRuleOper.equal;
import static cn.remex.web.service.BusinessService.ServiceType.AsyncService;

@Aspect
public class BusinessServiceAspect {
    private static Map<String, Object> cachedBsRvoMap = new HashMap<>();
    private static Map<String, Long> cachedExpiredMap = new HashMap<>();

    public static void resetCache(){
        cachedBsRvoMap = new HashMap<>();
        cachedExpiredMap = new HashMap<>();
    }


    @Around(value = "@annotation(cn.remex.web.service.BusinessService) && @annotation(bsAnno)")
    public Object doAroundBusinessService(final ProceedingJoinPoint pjp, BusinessService bsAnno) throws Throwable {
        if (bsAnno.needAuth()) {
//            if (!System.getProperty("os.name").toLowerCase().startsWith("win")) {
                Assert.isTrue(RemexContext.checkToken(), ServiceCode.ACCOUNT_NOT_AUTH, "用户未登录");
//            }
        }
        if (bsAnno.cachedArgIndexs().length > 0 || bsAnno.needCache()) {//指定需要缓存或者指定缓存的参数序号都开启缓存
            StringBuilder keyHash = new StringBuilder(pjp.getSignature().getDeclaringType().getName()+""+pjp.getSignature().getName());//bs and bs method name for cache prefix
            for ( int s : bsAnno.cachedArgIndexs()) {
                keyHash.append(pjp.getArgs()[s]==null?"0":pjp.getArgs()[s].hashCode()).append("-");
            }
            String key = keyHash.toString();
            Object retObj;
            if ((retObj = cachedBsRvoMap.get(keyHash.toString())) != null
                    && (null == cachedExpiredMap.get(key) || System.currentTimeMillis() - cachedExpiredMap.get(key) < bsAnno.cachedExpired() * 1000) )
                //如果有缓存结果，且缓存结果在有效期（没有日期戳或时间为过期）
                return retObj;
            else {
                //过期了，如果有结果就返回结果，通过开一个线程去重新计算
                Param retObj2 = new Param();
                Runnable runnable= ()->{
                    try {
                        retObj2.param = pjp.proceed();
                        cachedExpiredMap.put(key, System.currentTimeMillis());
                        cachedBsRvoMap.remove(key);
                        cachedBsRvoMap.put(key, retObj2.param);
                    } catch (Throwable throwable) {
                        //如果出错，清除缓存
                        cachedBsRvoMap.remove(key);
                        cachedExpiredMap.remove(key);
                        RemexConstants.logger.error("服务缓存后台线程异常 {}", throwable);
                    }
                };
                if (retObj != null) {
                    ThreadHelper.run(runnable);
                    return retObj;
                }else {
                    runnable.run();
                    return retObj2.param;
                }

            }

        }
        if (AsyncService.equals(bsAnno.type())) {
            return handleService(pjp, bsAnno);
        } else {
            return pjp.proceed();
        }

    }


    private Object handleService(final ProceedingJoinPoint pjp, BusinessService bsAnno) throws Throwable {
        Param<AsyncCvo> cvoParam = new Param<>(null);
        Arrays.asList(pjp.getArgs()).stream().filter(
                arg -> null != arg && arg instanceof AsyncCvo
        ).findFirst().ifPresent(cvo -> cvoParam.param = (AsyncCvo) cvo);
        AsyncCvo cvo = cvoParam.param;
        Assert.notNull(cvo, ServiceCode.BS_ERROR, "AsyncService 必须使用AsyncCvo!");
        Assert.notNull(cvo.getAsyncKey(), ServiceCode.BS_ERROR, "异步服务必须传asyncKey");

        Object ret = pjp.proceed();
        Assert.isTrue(ret != null && ret instanceof AsyncRvo, ServiceCode.BS_ERROR, "AsyncService 必须返回AsyncRvo");
        AsyncRvo rvo = (AsyncRvo) ret;

        SysStatus sysStatus = Database.createDbCvo(SysStatus.class).filterBy(SysStatus::getKey, equal, cvo.getAsyncKey())
                .orderBy(SysStatus::getModifyTime, Sort.DESC)
                .ready().queryBean();
        sysStatus = null == sysStatus ? new SysStatus() : sysStatus;
        sysStatus.setKey(cvo.getAsyncKey());
        rvo.setAsyncStatus(sysStatus);

        AsyncRvo retRvo = rvo.getProcessFunction().apply(rvo);
        if (null == sysStatus.getStatus() || Init.equals(sysStatus.getStatus()) //首次执行
                || ((Finish.equals(sysStatus.getStatus()) || Fail.equals(sysStatus.getStatus())) && cvo.getAsyncRestart())//重新执行
                ) {
            if (Finish.equals(sysStatus.getStatus()) || Fail.equals(sysStatus.getStatus())) {
                sysStatus.setProgressRate(0);
                sysStatus.setDesc("重新启动中...");
            }
            Assert.notNull(rvo.getStartFunction(), ServiceCode.BS_ERROR, "异步服务必须 通过asyncRvo.process(asyncRvo -> {//do you work} 注入更新逻辑");

            //注入更新状态的逻辑，从AsyncRvo中分离此逻辑有利于后期的扩展
            rvo._update(c -> ThreadHelper.run(() -> Database.getSession().store(c.getAsyncStatus())));
            //启动异步任务，如果需要JOB模式仅需要在次修改
            ThreadHelper.run(() -> {
                try {
                    rvo.getStartFunction().apply(rvo);
                    rvo._update(c -> ThreadHelper.run(() -> Database.getSession().store(c.getAsyncStatus())));
                } catch (Throwable t) {
                    RemexConstants.logger.error("异步服务出现错误：{}", t);
                    rvo.update(Fail, rvo.getAsyncStatus().getProgressRate(), t.toString());
                }
            });
            sysStatus.setStatus(Doing);
            Database.getSession().store(sysStatus);
        } else if (Finish.equals(sysStatus.getStatus()) && rvo.getSuccessFunction() != null) {
            retRvo = rvo.getSuccessFunction().apply(rvo);
            sysStatus.setStatus(Finish);
            Database.getSession().store(sysStatus);
        }

        return retRvo;
    }

}
